/*
  IniFile: a class for reading and writing initialisation files.

  The file is organised thus:

  item1 = value1
  item2 = value2
  item3 = value3
  ...

  [ Section 1 ]
  item1 = value1
  item2 = value2
  item3 = value3
  ...

  [ Section 2 ]
  item1 = value1
  item2 = value2
  item3 = value3
  ...

  where the first items are in the default empty section at the start of the
  file (logically, line 0 is "[]").

  Lines can be 1024 characters long. Section and item names are case
  insensitive. Leading space is always ignored; trailing space is ignored in
  sections and items, but not in values:

  "[Section]", "[ Section ]" and "[  Section   ]" are all equivalent.
  "item=" and "  item  =" are both equivalent.
  "=value" and "=  value" are equivalent, but "=  value  " is not.

  In other words, the value starts at the first non-space character after the
  equal sign, and extends to the end of the line.

  Values can contain strings, integers, doubles and boolean values (integers 0
  and non-zero, strings "True" and "False", "Yes" and "No", "On" and "Off").
  Strings do not strip quotes:

  item = "value"

  means the value == "\"value\"".

  Sectional square brackets do not have to match - the first and last are what
  counts:

  Line in file		Section name
  [Section]		  Section
  [ Section ]		  Section
  [[Section]]		  [Section]
  [Section]]		  Section]
  [ [ Section ] ]	  [ Section ]
  [ Section ] garbage	 invalid line

  Spacing is preserved when writing items:

  "   item   =   oldvalue"

  becomes

  "   item   =   newvalue"

  Values are expected to contain no leading space. (If a value does have lead-
  ing space, the spaces will be returned while the file is opened. When the
  file is later re-opened, the spaces will be ignored.)

  Section names can be duplicated; new items will be inserted after the last
  item in the last section:

  [ Section ]
  item1 = value1
  item2 = value2

  [ Section ]
  item3 = value3

  [ Section ]

  Adding a new item to Section will make it the first in the empty Section.

  If an item is duplicated, the FIRST one will be found (and hence, updated).
  (The duplicates will still be written, however.)

  Whole sections and individual items can be removed. When duplicates occur,
  the first will be removed; the rest will remain.

  It is also possible to retrieve all sections in the file, and all items in a
  section.

  Comments can only begin on a line (ignoring whitespace), and extend for the
  rest of that line. They start with any of the characters:

      # % ; \ / ' " | $ @ ! :

  Invalid lines are simply ignored. A line is invalid when a section does not
  have a closing square bracket, the closing bracket is not the last character
  (excluding space) on the line, or an item is not followed by an equal sign.


  Jason Hood. Public domain.
  29 November, 1996 - created due to deficiencies in TWS' ConfigFile.
   4 February, 1997 - Added GetItem() and GetInt().
  24 November to    - Removed all TWS influence (renamed from MyConfigFile and
   3 December	       used C++'s bool, removed the header);
		      wrote a templated linked list, and used it;
		      added dirty flag and isDirty() function;
		      added sections;
		      added Flush() function;
		      added GetBool() and WriteBool(), WriteTrueFalse(),
		       WriteYesNo() and WriteOnOff();
		      added GetDouble() and WriteDouble();
		      overloaded GetItem() and WriteItem() for integers, doubles
		       and bools;
		      added RemoveSection() and RemoveItem();
		      added RetrieveSections() and RetrieveItems();
		      wrote the description.
*/

#ifndef _inifile_h
#define _inifile_h

#include "list.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


class IniFile
{
public:
  bool Open(const char* filename);	// Read filename into memory
  bool Flush(); 			// Write memory to file
  bool Close(); 			// Write and free memory

  IniFile() : inifile(NULL), dirty(false) {}

  IniFile(const char* filename) : inifile(NULL), dirty(false)
  {
    Open(filename);
  }

  ~IniFile() { Close(); }


  // The default section ("[]") can be accessed with "" or NULL.

  // Return the value associated with item in section. If the item (or section)
  // does not exist, or no file has been opened, return the default value.
  char*  GetItem(  const char* section, const char* item, char*  defalt = "");
  int	 GetInt(   const char* section, const char* item, int	 defalt = 0);
  double GetDouble(const char* section, const char* item, double defalt = 0.0);
  bool	 GetBool(  const char* section, const char* item, bool defalt = false);

  int GetItem(const char* section, const char* item, int defalt)
  {
    return GetInt(section, item, defalt);
  }

  double GetItem(const char* section, const char* item, double defalt)
  {
    return GetDouble(section, item, defalt);
  }

  bool GetItem(const char* section, const char* item, bool defalt)
  {
    return GetBool(section, item, defalt);
  }

  // Update the item in section with value. Only returns false if no file has
  // been opened. The file is not updated.
  bool WriteItem(const char* section, const char* item, const char* value);

  bool WriteInt( const char* section, const char* item, int value)
  {
    return WriteItem(section, item, itoa(value, intbuffer, 10));
  }

  bool WriteDouble(const char* section, const char* item, double value)
  {
    return WriteItem(section, item,
		     (sprintf(intbuffer, "%g", value), intbuffer));
  }

  bool WriteBool(const char* section, const char* item, bool value)
  {
    return WriteInt(section, item, value);
  }

  bool WriteItem(const char* section, const char* item, int value)
  {
    return WriteInt(section, item, value);
  }

  bool WriteItem(const char* section, const char* item, double value)
  {
    return WriteDouble(section, item, value);
  }

  bool WriteItem(const char* section, const char* item, bool value)
  {
    return WriteBool(section, item, value);
  }

  bool WriteTrueFalse(const char* section, const char* item, bool value)
  {
    return WriteItem(section, item, (value) ? "True" : "False");
  }

  bool WriteYesNo(const char* section, const char* item, bool value)
  {
    return WriteItem(section, item, (value) ? "Yes" : "No");
  }

  bool WriteOnOff(const char* section, const char* item, bool value)
  {
    return WriteItem(section, item, (value) ? "On" : "Off");
  }

  // Remove the section from the file. Returns false if the section doesn't
  // exist.
  bool RemoveSection(const char* section);

  // Remove item from section. Returns false if the item (or section) doesn't
  // exist.
  bool RemoveItem(const char* section, const char* item);

  // Return the number of sections in the file, and set array pointing to them.
  // The memory allocated to array should be deleted by the caller.
  size_t RetrieveSections(const char**& array);

  // Return the number of items in section, and set array pointing to them.
  // The memory allocated to array should be deleted by the caller.
  size_t RetrieveItems(const char* section, const char**& array);

  // True if an item has been added/changed and the file needs to be updated.
  bool isDirty() const { return dirty; }


private:
  char *inifile, intbuffer[33];
  bool dirty;				// Should the file be written?

  struct Line
  {
    char *line, 			// The line read from the file
	 *end,				// End of the section/item name
	  ch;				// Character replaced with null
    Line() : line(NULL), end(NULL) {}
    ~Line() { free(line); }
  };
  typedef List<Line> LineList;
  typedef Node<Line> LineNode;

  struct Item
  {
    LineNode* line;			// The line containing the item
    char *item, 			// Pointer into the above
	 *value;			// Ditto
    Item() : item(NULL) {}
  };
  typedef List<Item> ItemList;

  struct Section
  {
    const char* section;		// The section name ("[ section ]")
    ItemList	items;			// The list of items in the section
    LineNode*	sect;			// The line containing the name
    LineNode*	line;			// Insert a new item after this line
    Section() : section(NULL) {}
    Section(const char* name, LineNode* ins) : section(name), sect(ins),
							      line(ins) {}
  };
  typedef List<Section> SectionList;

  LineList    lineList;
  SectionList sectionList;

  void	   AddSection(LineNode* line, char* section, ItemList*& items);
  Section* FindSection(const char* section);
  Item*    FindItem(ItemList& section, const char* item);
};

#endif
